Remove and Add Elements

Author: @WJinger

Import some packages

from collections import namedtuple
from typing import Any, Union

import matplotlib.pyplot as plt
import numpy as np
import openseespy.opensees as ops

import opstool as opst
import opstool.vis.plotly as opsplt

Set base variables

# Set unit
UNIT = opst.pre.UnitSystem(length="m", force="kn", time="sec")

# Set constant
g = 9.80665 * (UNIT.m / UNIT.sec**2)
Ubig = 1.0e10
Usmall = 1.0e-10

# Set data path
# data_path = "./OutData"
# os.makedirs(data_path, exist_ok=True)
# opst.post.set_odb_path(data_path)  # set opstool ODB path

Section creator function

def secCreate(sec_name: str, secTag: int, matTag: int, w: float, h: float, info: bool = True) -> dict[str, Any]:
    """
    # Just for a simple rectangle section

    :param sec_name: section name
    :param secTag: section tag
    :param matTag: material tag
    :param w: width
    :param h: height
    :param info: if True, print section information

    :return: a dictionary with the section properties

    """

    # section outline
    sec_outline = [[0, 0], [w, 0], [w, h], [0, h]]
    inner_geo = opst.pre.section.create_polygon_patch(sec_outline)
    SEC = opst.pre.section.FiberSecMesh(sec_name=sec_name)
    SEC.add_patch_group({"inner": inner_geo})
    SEC.set_ops_mat_tag({"inner": matTag})
    SEC.set_mesh_size({"inner": (w + h) / 20.0})
    SEC.set_mesh_color({"inner": "lightblue"})
    SEC.mesh()

    # section properties
    SEC.centring()
    sec_props = SEC.get_frame_props(display_results=info)  # print section properties

    if info:
        SEC.view(fill=True, show_legend=True, aspect_ratio="equal")
        plt.show()

    # define section
    SEC.to_opspy_cmds(secTag=secTag, G=100 * UNIT.gpa)

    return sec_props

Model creator function

Values for return

RETURN_VAL = namedtuple(
    "RETURN_VAL",
    ["ctrl_node", "remove_ele", "add_ele", "top_link_ele", "base_link_ele"],
)
"""
Remove element:
    ops.remove('ele', RETURN_VAL.remove_ele)
Add element:
    ops.element(*RETURN_VAL.add_ele)
"""

Model creater function

def triangleStruc(top_free: bool = False, base_free: bool = False, info: bool = True) -> RETURN_VAL:
    """
    # Create a simple triangle structure

    :param top_free: if True, add zeroLength element to the top
    :param base_free: if True, add zeroLengthSection element to the base
    :param info: if True, print section element information

    :return a namedtuple with the following values:
    - :ctrl_node: the control node for static analysis
    - :remove_ele: the element to be removed
    - :add_ele: the element to be added
    - :top_link_ele: the top zeroLength element
    - :base_link_ele: the base zeroLengthSection element
    """

    "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----"
    # Initialize
    ops.wipe()
    ops.model("basic", "-ndm", 3, "-ndf", 6)

    # For vertical element
    transf_ver = 1
    ops.geomTransf("PDelta", transf_ver, *(-1, 0, 0))
    # For other element
    transf_other = 2
    ops.geomTransf("PDelta", transf_other, *(0, 0, 1))

    "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----"
    # Steel material
    steelTag = 10
    ops.uniaxialMaterial("Steel02", steelTag, 400 * UNIT.mpa, 206 * UNIT.gpa, 0.01)

    # Elastic no tension material
    ENTTag = 20
    ops.uniaxialMaterial("ENT", ENTTag, 200 * UNIT.gpa)

    # Elastic material
    elasticTag = 30
    ops.uniaxialMaterial("Elastic", elasticTag, 1.0e6)

    "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----"
    # Define vertical element section
    secTag_1 = 1
    W_1 = 10.0 * UNIT.cm
    H_1 = 10.0 * UNIT.cm
    sec_props_1 = secCreate(sec_name="vertical", secTag=secTag_1, matTag=steelTag, w=W_1, h=H_1, info=info)

    # Define vertical base link element section
    secTag_2 = 2
    sec_props_2 = secCreate(
        sec_name="vertical base",
        secTag=secTag_2,
        matTag=ENTTag,
        w=W_1,
        h=H_1,
        info=info,
    )

    # Define other element section
    secTag_3 = 3
    W_2 = 2.0 * UNIT.cm
    H_2 = 2.0 * UNIT.cm
    sec_props_3 = secCreate(sec_name="oblique", secTag=secTag_3, matTag=steelTag, w=W_2, h=H_2, info=info)

    "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----"
    # Node Tags
    nodeTag_base = 1
    nodeTag_top = 2
    nodeTag_base_link = 3  # Base link node
    nodeTag_top_link = 4  # Top link node
    nodeTag_left_base = 5
    nodeTag_right_base = 6

    # Element Tags
    eleTag_main = 1
    eleTag_left = 2
    eleTag_right = 3
    eleTag_base_link = 4  # Base link element
    eleTag_top_link = 5  # Top link element

    "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----"
    # Create nodes
    ops.node(nodeTag_base, 0.0, 0.0, 0.0)
    ops.node(nodeTag_top, 0.0, 0.0, 1.0 * UNIT.m)
    ops.node(nodeTag_left_base, 0.0, -1.0 * UNIT.m, 0.0)
    ops.node(nodeTag_right_base, 0.0, 1.0 * UNIT.m, 0.0)

    # Nodes fix
    ops.fix(nodeTag_base, 1, 1, 1, 1, 1, 1)
    ops.fix(nodeTag_left_base, 1, 1, 1, 1, 1, 1)
    ops.fix(nodeTag_right_base, 1, 1, 1, 1, 1, 1)

    # Integration
    npTag_1 = 1
    npTag_2 = 2
    ops.beamIntegration("Legendre", npTag_1, secTag_1, 5)
    ops.beamIntegration("Legendre", npTag_2, secTag_3, 5)

    "# ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----"
    """
    Note:
        In order to check the local axes of zeroLength and zeroLengthSection, the length of zeroLength and zeroLengthSection elements is not zero.
        It causes opensees warning but does not affect the results.
    """

    if top_free and not base_free:
        print("CASE: zeroLength")

        ops.node(nodeTag_top_link, 0.0, 0.0, 1.2 * UNIT.m)  # For top zeroLength
        ops.element(
            "dispBeamColumn",
            eleTag_main,
            *(nodeTag_top, nodeTag_base),
            transf_ver,
            npTag_1,
        )  # Single vertical element
        ops.element(
            "dispBeamColumn",
            eleTag_left,
            *(nodeTag_top, nodeTag_left_base),
            transf_ver,
            npTag_2,
        )  # Left oblique element

        mats = (elasticTag, elasticTag, elasticTag, elasticTag, elasticTag, elasticTag)
        ops.element(
            "zeroLength",
            eleTag_top_link,
            *(nodeTag_top_link, nodeTag_top),
            "-mat",
            *mats,
            "-dir",
            *(1, 2, 3, 4, 5, 6),
            "-orient",
            *(0, 0, -1),
            *(0, -1, 0),
        )

        return_val = RETURN_VAL(
            ctrl_node=nodeTag_top_link,
            remove_ele=eleTag_left,
            add_ele=(
                "dispBeamColumn",
                eleTag_right,
                *(nodeTag_top, nodeTag_right_base),
                transf_ver,
                npTag_2,
            ),
            top_link_ele=eleTag_top_link,
            base_link_ele=None,
        )

    elif not top_free and base_free:
        print("CASE: zeroLengthSection")

        ops.node(nodeTag_base_link, 0.0, 0.0, 0.2 * UNIT.m)  # For base zeroLengthSection
        ops.element(
            "dispBeamColumn",
            eleTag_main,
            *(nodeTag_top, nodeTag_base_link),
            transf_ver,
            npTag_1,
        )  # Single vertical element
        ops.element(
            "dispBeamColumn",
            eleTag_left,
            *(nodeTag_top, nodeTag_left_base),
            transf_ver,
            npTag_2,
        )  # Left oblique element

        ops.equalDOF(*(nodeTag_base, nodeTag_base_link), *(1, 2, 3))
        ops.element(
            "zeroLengthSection",
            eleTag_base_link,
            *(nodeTag_base_link, nodeTag_base),
            secTag_2,
            "-orient",
            *(0, 0, -1),
            *(0, -1, 0),
        )

        return_val = RETURN_VAL(
            ctrl_node=nodeTag_top,
            remove_ele=eleTag_left,
            add_ele=(
                "dispBeamColumn",
                eleTag_right,
                *(nodeTag_top, nodeTag_right_base),
                transf_ver,
                npTag_2,
            ),
            top_link_ele=None,
            base_link_ele=eleTag_base_link,
        )

    elif top_free and base_free:
        print("CASE: zeroLength & zeroLengthSection")

        ops.node(nodeTag_top_link, 0.0, 0.0, 1.2 * UNIT.m)  # For top zeroLength
        ops.node(nodeTag_base_link, 0.0, 0.0, 0.2 * UNIT.m)  # For base zeroLengthSection
        ops.element(
            "dispBeamColumn",
            eleTag_main,
            *(nodeTag_top, nodeTag_base_link),
            transf_ver,
            npTag_1,
        )  # Single vertical element
        ops.element(
            "dispBeamColumn",
            eleTag_left,
            *(nodeTag_top, nodeTag_left_base),
            transf_ver,
            npTag_2,
        )  # Left oblique element

        mats = (elasticTag, elasticTag, elasticTag, elasticTag, elasticTag, elasticTag)
        ops.element(
            "zeroLength",
            eleTag_top_link,
            *(nodeTag_top_link, nodeTag_top),
            "-mat",
            *mats,
            "-dir",
            *(1, 2, 3, 4, 5, 6),
            "-orient",
            *(0, 0, -1),
            *(0, -1, 0),
        )

        ops.equalDOF(*(nodeTag_base, nodeTag_base_link), *(1, 2, 3))
        ops.element(
            "zeroLengthSection",
            eleTag_base_link,
            *(nodeTag_base_link, nodeTag_base),
            secTag_2,
            "-orient",
            *(0, 0, -1),
            *(0, -1, 0),
        )

        return_val = RETURN_VAL(
            ctrl_node=nodeTag_top_link,
            remove_ele=eleTag_left,
            add_ele=(
                "dispBeamColumn",
                eleTag_right,
                *(nodeTag_top, nodeTag_right_base),
                transf_ver,
                npTag_2,
            ),
            top_link_ele=eleTag_top_link,
            base_link_ele=eleTag_base_link,
        )

    else:
        print("CASE: None")

        ops.element(
            "dispBeamColumn",
            eleTag_main,
            *(nodeTag_top, nodeTag_base),
            transf_ver,
            npTag_1,
        )  # Single vertical element
        ops.element(
            "dispBeamColumn",
            eleTag_left,
            *(nodeTag_top, nodeTag_left_base),
            transf_ver,
            npTag_2,
        )  # Left oblique element

        return_val = RETURN_VAL(
            ctrl_node=nodeTag_top,
            remove_ele=eleTag_left,
            add_ele=(
                "dispBeamColumn",
                eleTag_right,
                *(nodeTag_top, nodeTag_right_base),
                transf_ver,
                npTag_2,
            ),
            top_link_ele=None,
            base_link_ele=None,
        )

    return return_val

Analysis function

def analysisLib(
    targets: Union[list, tuple, np.ndarray],
    patternTag: int,
    ctrl_node: int,
    ODB: opst.post.CreateODB,
) -> tuple[np.ndarray, np.ndarray]:
    """
    # Static analysis function

    :param targets: Displacement path
    :param patternTag: Pattern tag
    :param ctrl_node: Control node tag
    :param ODB: CreateODB object

    :return: Displacement and force
    """

    ops.system("BandGeneral")
    ops.constraints("Transformation")
    ops.numberer("RCM")

    analysis = opst.anlys.SmartAnalyze("Static")
    segs = analysis.static_split(targets=targets, maxStep=0.01 * UNIT.m)

    force_lambda: Union[list, float] = [0.0]
    node_disp: Union[list, float] = [0.0]
    for seg in segs:
        ok = analysis.StaticAnalyze(node=ctrl_node, dof=2, seg=seg)  # node tag 1, dof 2
        if ok < 0:
            raise RuntimeError("Analysis failed")  # noqa: TRY003

        # Fetch response
        ODB.fetch_response_step()
        force_lambda.append(ops.getLoadFactor(patternTag))
        node_disp.append(ops.nodeDisp(ctrl_node, 2))

    return np.array(node_disp), np.array(force_lambda)

Check single column hysteretic curve

For Case 1, I just want to look at the hysteresis curve with only vertical element.

# Case
CASE_1 = 1
# Create model
model_info = triangleStruc(top_free=True, base_free=True, info=True)
  • vertical
  • vertical base
  • oblique
OPSTOOL™ :: The section vertical has been successfully meshed!
                           Frame Section Properties
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Symbol    ┃ Value                  ┃ Definition                             ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ A         │ 1.000E-02              │ Cross-sectional area                   │
│ centroid  │ (0.000E+00, 0.000E+00) │ Elastic centroid                       │
│ Iy        │ 8.333E-06              │ Moment of inertia y-axis               │
│ Iz        │ 8.333E-06              │ Moment of inertia z-axis               │
│ Iyz       │ 1.694E-20              │ Product of inertia                     │
│ Wyt       │ 1.667E-04              │ Section moduli of top fibres y-axis    │
│ Wyb       │ 1.667E-04              │ Section moduli of bottom fibres y-axis │
│ Wzt       │ 1.667E-04              │ Section moduli of top fibres z-axis    │
│ Wzb       │ 1.667E-04              │ Section moduli of bottom fibres z-axis │
│ J         │ 1.406E-05              │ Torsion constant                       │
│ phi       │ 0.000E+00              │ Principal axis angle                   │
│ rho_rebar │ 0.000E+00              │ Ratio of reinforcement                 │
└───────────┴────────────────────────┴────────────────────────────────────────┘
OPSTOOL™ :: The section vertical base has been successfully meshed!
                           Frame Section Properties
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Symbol    ┃ Value                  ┃ Definition                             ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ A         │ 1.000E-02              │ Cross-sectional area                   │
│ centroid  │ (0.000E+00, 0.000E+00) │ Elastic centroid                       │
│ Iy        │ 8.333E-06              │ Moment of inertia y-axis               │
│ Iz        │ 8.333E-06              │ Moment of inertia z-axis               │
│ Iyz       │ 1.694E-20              │ Product of inertia                     │
│ Wyt       │ 1.667E-04              │ Section moduli of top fibres y-axis    │
│ Wyb       │ 1.667E-04              │ Section moduli of bottom fibres y-axis │
│ Wzt       │ 1.667E-04              │ Section moduli of top fibres z-axis    │
│ Wzb       │ 1.667E-04              │ Section moduli of bottom fibres z-axis │
│ J         │ 1.406E-05              │ Torsion constant                       │
│ phi       │ 0.000E+00              │ Principal axis angle                   │
│ rho_rebar │ 0.000E+00              │ Ratio of reinforcement                 │
└───────────┴────────────────────────┴────────────────────────────────────────┘
OPSTOOL™ :: The section oblique has been successfully meshed!
                           Frame Section Properties
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Symbol    ┃ Value                  ┃ Definition                             ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ A         │ 4.000E-04              │ Cross-sectional area                   │
│ centroid  │ (0.000E+00, 0.000E+00) │ Elastic centroid                       │
│ Iy        │ 1.333E-08              │ Moment of inertia y-axis               │
│ Iz        │ 1.333E-08              │ Moment of inertia z-axis               │
│ Iyz       │ 0.000E+00              │ Product of inertia                     │
│ Wyt       │ 1.333E-06              │ Section moduli of top fibres y-axis    │
│ Wyb       │ 1.333E-06              │ Section moduli of bottom fibres y-axis │
│ Wzt       │ 1.333E-06              │ Section moduli of top fibres z-axis    │
│ Wzb       │ 1.333E-06              │ Section moduli of bottom fibres z-axis │
│ J         │ 2.249E-08              │ Torsion constant                       │
│ phi       │ 0.000E+00              │ Principal axis angle                   │
│ rho_rebar │ 0.000E+00              │ Ratio of reinforcement                 │
└───────────┴────────────────────────┴────────────────────────────────────────┘
CASE: zeroLength & zeroLengthSection
WARNING ZeroLength::setDomain(): Element 5 has L= 0.2, which is greater than the tolerance
ZeroLengthSection::setDomain() -- Element 4has L= 0.2, which is greater than the tolerance

Remove oblique element

ops.remove("ele", model_info.remove_ele)

# Create data base
ODB = opst.post.CreateODB(odb_tag=CASE_1, save_fiber_sec_resp=True, fiber_ele_tags=[1, 2, 3, 4], model_update=True)

# Linear timeSeries
ts = 1
ops.timeSeries("Linear", ts)

Pattern for static analysis

pattern = 100
ops.pattern("Plain", pattern, ts)
ops.load(model_info.ctrl_node, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0)

Displacement path

disp_path = (
    np.array([
        0.0,
        0.05,
        -0.05,
        0.10,
        -0.10,
        0.15,
        -0.15,
        0.20,
        -0.20,
        0.25,
        -0.25,
        0.30,
        -0.30,
        0.0,
    ])
    * UNIT.m
)

Analysis

disp, force = analysisLib(targets=disp_path, patternTag=pattern, ctrl_node=model_info.ctrl_node, ODB=ODB)
# Save data base
ODB.save_response()
🚀 OPSTOOL::SmartAnalyze:   0%|                                                    | 0/420 [00:00<?, ? step/s]
🚀 OPSTOOL::SmartAnalyze:   4%|█▊                                        | 18/420 [00:00<00:02, 179.12 step/s]
🚀 OPSTOOL::SmartAnalyze:   9%|███▋                                      | 37/420 [00:00<00:02, 183.32 step/s]
🚀 OPSTOOL::SmartAnalyze:  13%|█████▌                                    | 56/420 [00:00<00:01, 184.93 step/s]
🚀 OPSTOOL::SmartAnalyze:  18%|███████▌                                  | 75/420 [00:00<00:01, 185.76 step/s]
🚀 OPSTOOL::SmartAnalyze:  22%|█████████▍                                | 94/420 [00:00<00:01, 186.22 step/s]
🚀 OPSTOOL::SmartAnalyze:  27%|███████████                              | 113/420 [00:00<00:01, 183.77 step/s]
🚀 OPSTOOL::SmartAnalyze:  31%|████████████▉                            | 132/420 [00:00<00:01, 183.50 step/s]
🚀 OPSTOOL::SmartAnalyze:  36%|██████████████▋                          | 151/420 [00:00<00:01, 166.22 step/s]
🚀 OPSTOOL::SmartAnalyze:  40%|████████████████▍                        | 169/420 [00:00<00:01, 169.66 step/s]
🚀 OPSTOOL::SmartAnalyze:  45%|██████████████████▎                      | 188/420 [00:01<00:01, 174.29 step/s]
🚀 OPSTOOL::SmartAnalyze:  50%|████████████████████▎                    | 208/420 [00:01<00:01, 178.70 step/s]
🚀 OPSTOOL::SmartAnalyze:  54%|██████████████████████▏                  | 227/420 [00:01<00:01, 181.30 step/s]
🚀 OPSTOOL::SmartAnalyze:  59%|████████████████████████                 | 246/420 [00:01<00:00, 183.36 step/s]
🚀 OPSTOOL::SmartAnalyze:  63%|█████████████████████████▊               | 265/420 [00:01<00:00, 183.76 step/s]
🚀 OPSTOOL::SmartAnalyze:  68%|███████████████████████████▋             | 284/420 [00:01<00:00, 181.91 step/s]
🚀 OPSTOOL::SmartAnalyze:  72%|█████████████████████████████▌           | 303/420 [00:01<00:00, 182.37 step/s]
🚀 OPSTOOL::SmartAnalyze:  77%|███████████████████████████████▍         | 322/420 [00:01<00:00, 183.84 step/s]
🚀 OPSTOOL::SmartAnalyze:  81%|█████████████████████████████████▎       | 341/420 [00:01<00:00, 182.46 step/s]
🚀 OPSTOOL::SmartAnalyze:  86%|███████████████████████████████████▏     | 360/420 [00:01<00:00, 183.76 step/s]
🚀 OPSTOOL::SmartAnalyze:  90%|████████████████████████████████████▉    | 379/420 [00:02<00:00, 184.04 step/s]
🚀 OPSTOOL::SmartAnalyze:  95%|██████████████████████████████████████▊  | 398/420 [00:02<00:00, 132.10 step/s]
🚀 OPSTOOL::SmartAnalyze:  99%|████████████████████████████████████████▋| 417/420 [00:02<00:00, 144.72 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 420/420 [00:02<00:00, 144.72 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 420/420 [00:02<00:00, 171.53 step/s]
Note: OpenSees LogFile has been generated in .SmartAnalyze-OpenSees.log.
>>> 🎉 OPSTOOL::SmartAnalyze:: Successfully finished! Time consumption: 2.450 s. 🎉
OPSTOOL™ ::  All responses data with _odb_tag = 1 saved in G:\opstool\docs\.opstool.output\RespStepData-1.odb!

View results

plt.close("all")
plt.title("Single Column Hysteretic Curve")
plt.plot(disp, force, linewidth=1.0, label="Single Column", zorder=2)
plt.xlim(-0.4, 0.4)
plt.ylim(-500 * UNIT.kn, 500 * UNIT.kn)
plt.xlabel("Displacement (m)")
plt.ylabel("Force (kN)")
plt.legend(loc="lower right", bbox_to_anchor=(1.0, 0.0))
plt.grid(linestyle="--", linewidth=0.5, zorder=1)
plt.show()
Single Column Hysteretic Curve

Visualize model response

fig = opsplt.plot_nodal_responses_animation(odb_tag=CASE_1, resp_type="disp", show_undeformed=True)
fig
# fig.show()
OPSTOOL™ ::  Loading responses data from G:\opstool\docs\.opstool.output\RespStepData-1.odb ...


Remove and add element during analysis

# Case
CASE_2 = 2

"""Ok, For case 2 I want to remove and add element during analysis."""

# Create model
model_info = triangleStruc(top_free=True, base_free=True, info=False)

# Create data base
ODB = opst.post.CreateODB(odb_tag=CASE_2, fiber_ele_tags="ALL", model_update=True)

# Linear timeSeries
ts = 1
ops.timeSeries("Linear", ts)

# Pattern for static analysis
pattern_1 = 100
ops.pattern("Plain", pattern_1, ts)
ops.load(model_info.ctrl_node, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0)

# Displacement path
disp_path_1 = (
    np.array([
        0.0,
        0.05,
        -0.05,
        0.10,
        -0.10,
        0.15,
        -0.15,
        # 0.20, -0.20,
        # 0.25, -0.25,
        # 0.30, -0.30,
        0.0,
    ])
    * UNIT.m
)

# Analysis
disp_1, force_1 = analysisLib(targets=disp_path_1, patternTag=pattern_1, ctrl_node=model_info.ctrl_node, ODB=ODB)
OPSTOOL™ :: The section vertical has been successfully meshed!
OPSTOOL™ :: The section vertical base has been successfully meshed!
OPSTOOL™ :: The section oblique has been successfully meshed!
CASE: zeroLength & zeroLengthSection

🚀 OPSTOOL::SmartAnalyze:   0%|                                                    | 0/120 [00:00<?, ? step/s]
🚀 OPSTOOL::SmartAnalyze:  14%|█████▉                                    | 17/120 [00:00<00:00, 161.85 step/s]
🚀 OPSTOOL::SmartAnalyze:  28%|███████████▉                              | 34/120 [00:00<00:00, 156.07 step/s]
🚀 OPSTOOL::SmartAnalyze:  42%|█████████████████▌                        | 50/120 [00:00<00:00, 154.06 step/s]
🚀 OPSTOOL::SmartAnalyze:  55%|███████████████████████                   | 66/120 [00:00<00:00, 154.76 step/s]
🚀 OPSTOOL::SmartAnalyze:  68%|████████████████████████████▋             | 82/120 [00:00<00:00, 152.93 step/s]
🚀 OPSTOOL::SmartAnalyze:  82%|██████████████████████████████████▎       | 98/120 [00:00<00:00, 152.33 step/s]
🚀 OPSTOOL::SmartAnalyze:  95%|██████████████████████████████████████▉  | 114/120 [00:00<00:00, 152.04 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 120/120 [00:00<00:00, 152.04 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 120/120 [00:00<00:00, 153.53 step/s]
Note: OpenSees LogFile has been generated in .SmartAnalyze-OpenSees.log.
>>> 🎉 OPSTOOL::SmartAnalyze:: Successfully finished! Time consumption: 0.782 s. 🎉

Remove element

ops.loadConst("-time", 0.0)  # Important !!!
ops.remove("ele", model_info.remove_ele)  # Remove element

# Pattern for static analysis (After remove element)
pattern_2 = 200
ops.pattern("Plain", pattern_2, ts)
ops.load(model_info.ctrl_node, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0)

# Displacement path (After remove element)
disp_path_2 = (
    np.array([
        0.0,
        # 0.05, -0.05,
        # 0.10, -0.10,
        # 0.15, -0.15,
        0.20,
        -0.20,
        0.25,
        -0.25,
        0.30,
        -0.30,
        0.0,
    ])
    * UNIT.m
)

# Analysis (After remove element)
disp_2, force_2 = analysisLib(targets=disp_path_2, patternTag=pattern_2, ctrl_node=model_info.ctrl_node, ODB=ODB)
🚀 OPSTOOL::SmartAnalyze:   0%|                                                    | 0/300 [00:00<?, ? step/s]
🚀 OPSTOOL::SmartAnalyze:   6%|██▋                                       | 19/300 [00:00<00:01, 185.41 step/s]
🚀 OPSTOOL::SmartAnalyze:  13%|█████▎                                    | 38/300 [00:00<00:01, 183.92 step/s]
🚀 OPSTOOL::SmartAnalyze:  19%|███████▉                                  | 57/300 [00:00<00:01, 183.31 step/s]
🚀 OPSTOOL::SmartAnalyze:  25%|██████████▋                               | 76/300 [00:00<00:01, 182.57 step/s]
🚀 OPSTOOL::SmartAnalyze:  32%|█████████████▎                            | 95/300 [00:00<00:01, 121.53 step/s]
🚀 OPSTOOL::SmartAnalyze:  38%|███████████████▌                         | 114/300 [00:00<00:01, 137.21 step/s]
🚀 OPSTOOL::SmartAnalyze:  44%|██████████████████▏                      | 133/300 [00:00<00:01, 150.60 step/s]
🚀 OPSTOOL::SmartAnalyze:  50%|████████████████████▋                    | 151/300 [00:00<00:00, 156.82 step/s]
🚀 OPSTOOL::SmartAnalyze:  56%|███████████████████████                  | 169/300 [00:01<00:00, 163.10 step/s]
🚀 OPSTOOL::SmartAnalyze:  62%|█████████████████████████▌               | 187/300 [00:01<00:00, 167.03 step/s]
🚀 OPSTOOL::SmartAnalyze:  69%|████████████████████████████▏            | 206/300 [00:01<00:00, 171.75 step/s]
🚀 OPSTOOL::SmartAnalyze:  75%|██████████████████████████████▌          | 224/300 [00:01<00:00, 173.36 step/s]
🚀 OPSTOOL::SmartAnalyze:  81%|█████████████████████████████████▏       | 243/300 [00:01<00:00, 175.96 step/s]
🚀 OPSTOOL::SmartAnalyze:  88%|███████████████████████████████████▉     | 263/300 [00:01<00:00, 180.63 step/s]
🚀 OPSTOOL::SmartAnalyze:  94%|██████████████████████████████████████▌  | 282/300 [00:01<00:00, 182.10 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 300/300 [00:01<00:00, 182.10 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 300/300 [00:01<00:00, 167.54 step/s]
Note: OpenSees LogFile has been generated in .SmartAnalyze-OpenSees.log.
>>> 🎉 OPSTOOL::SmartAnalyze:: Successfully finished! Time consumption: 1.792 s. 🎉

Add element

ops.loadConst("-time", 0.0)  # Important !!!
ops.element(*model_info.add_ele)  # Add element

# Pattern for static analysis (After add element)
pattern_3 = 300
ops.pattern("Plain", pattern_3, ts)
ops.load(model_info.ctrl_node, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0)

# Displacement path (After add element)
disp_path_3 = (
    np.array([
        0.0,
        0.05,
        -0.05,
        0.10,
        -0.10,
        0.15,
        -0.15,
        0.20,
        -0.20,
        0.25,
        -0.25,
        0.30,
        -0.30,
        0.0,
    ])
    * UNIT.m
)

# Analysis (After add element)
disp_3, force_3 = analysisLib(targets=disp_path_3, patternTag=pattern_3, ctrl_node=model_info.ctrl_node, ODB=ODB)
🚀 OPSTOOL::SmartAnalyze:   0%|                                                    | 0/420 [00:00<?, ? step/s]
🚀 OPSTOOL::SmartAnalyze:   4%|█▌                                        | 16/420 [00:00<00:02, 154.70 step/s]
🚀 OPSTOOL::SmartAnalyze:   8%|███▏                                      | 32/420 [00:00<00:02, 151.52 step/s]
🚀 OPSTOOL::SmartAnalyze:  11%|████▊                                     | 48/420 [00:00<00:02, 152.90 step/s]
🚀 OPSTOOL::SmartAnalyze:  15%|██████▍                                   | 64/420 [00:00<00:02, 152.19 step/s]
🚀 OPSTOOL::SmartAnalyze:  19%|████████                                  | 80/420 [00:00<00:02, 151.51 step/s]
🚀 OPSTOOL::SmartAnalyze:  23%|█████████▌                                | 96/420 [00:00<00:02, 152.52 step/s]
🚀 OPSTOOL::SmartAnalyze:  27%|██████████▉                              | 112/420 [00:00<00:02, 151.55 step/s]
🚀 OPSTOOL::SmartAnalyze:  30%|████████████▍                            | 128/420 [00:00<00:01, 152.99 step/s]
🚀 OPSTOOL::SmartAnalyze:  34%|██████████████                           | 144/420 [00:00<00:01, 151.16 step/s]
🚀 OPSTOOL::SmartAnalyze:  38%|███████████████▌                         | 160/420 [00:01<00:01, 151.07 step/s]
🚀 OPSTOOL::SmartAnalyze:  42%|█████████████████▏                       | 176/420 [00:01<00:01, 152.54 step/s]
🚀 OPSTOOL::SmartAnalyze:  46%|██████████████████▋                      | 192/420 [00:01<00:01, 151.50 step/s]
🚀 OPSTOOL::SmartAnalyze:  50%|████████████████████▎                    | 208/420 [00:01<00:01, 150.70 step/s]
🚀 OPSTOOL::SmartAnalyze:  53%|█████████████████████▊                   | 224/420 [00:01<00:01, 149.59 step/s]
🚀 OPSTOOL::SmartAnalyze:  57%|███████████████████████▎                 | 239/420 [00:01<00:01, 148.66 step/s]
🚀 OPSTOOL::SmartAnalyze:  61%|████████████████████████▉                | 255/420 [00:01<00:01, 150.74 step/s]
🚀 OPSTOOL::SmartAnalyze:  65%|██████████████████████████▍              | 271/420 [00:01<00:00, 151.75 step/s]
🚀 OPSTOOL::SmartAnalyze:  68%|████████████████████████████             | 287/420 [00:01<00:00, 148.47 step/s]
🚀 OPSTOOL::SmartAnalyze:  72%|█████████████████████████████▍           | 302/420 [00:02<00:01, 101.87 step/s]
🚀 OPSTOOL::SmartAnalyze:  76%|███████████████████████████████          | 318/420 [00:02<00:00, 113.44 step/s]
🚀 OPSTOOL::SmartAnalyze:  79%|████████████████████████████████▌        | 333/420 [00:02<00:00, 120.81 step/s]
🚀 OPSTOOL::SmartAnalyze:  83%|██████████████████████████████████       | 349/420 [00:02<00:00, 128.94 step/s]
🚀 OPSTOOL::SmartAnalyze:  87%|███████████████████████████████████▋     | 365/420 [00:02<00:00, 135.19 step/s]
🚀 OPSTOOL::SmartAnalyze:  91%|█████████████████████████████████████▏   | 381/420 [00:02<00:00, 141.11 step/s]
🚀 OPSTOOL::SmartAnalyze:  94%|██████████████████████████████████████▋  | 396/420 [00:02<00:00, 142.39 step/s]
🚀 OPSTOOL::SmartAnalyze:  98%|████████████████████████████████████████▏| 412/420 [00:02<00:00, 144.90 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 420/420 [00:02<00:00, 144.90 step/s]
🚀 OPSTOOL::SmartAnalyze: 100%|█████████████████████████████████████████| 420/420 [00:02<00:00, 142.59 step/s]
Note: OpenSees LogFile has been generated in .SmartAnalyze-OpenSees.log.
>>> 🎉 OPSTOOL::SmartAnalyze:: Successfully finished! Time consumption: 2.945 s. 🎉

Save data base

ODB.save_response()
OPSTOOL™ ::  All responses data with _odb_tag = 2 saved in G:\opstool\docs\.opstool.output\RespStepData-2.odb!

View results (After remove element) sphinx_gallery_thumbnail_number = 6

plt.close("all")
plt.title("Triangle Remove Element Hysteretic Curve")

plt.plot(
    disp,
    force,
    label="Single Column",
    color="gray",
    linewidth=1.5,
    linestyle="--",
    zorder=2,
)
plt.plot(disp_1, force_1, linewidth=1.0, label="Before Remove", zorder=3)
plt.plot(disp_2, force_2 + force_1[-1], linewidth=1.0, label="After Remove", zorder=3)

plt.xlim(-0.4, 0.4)
plt.ylim(-500 * UNIT.kn, 500 * UNIT.kn)
plt.xlabel("Displacement (m)")
plt.ylabel("Force (kN)")
plt.legend(loc="lower right", bbox_to_anchor=(1.0, 0.0))
plt.grid(linestyle="--", linewidth=0.5, zorder=1)
plt.show()
Triangle Remove Element Hysteretic Curve

View results (After add element)

plt.close("all")
plt.title("Demaged Single Column Add Element Hysteretic Curve")

plt.plot(
    disp,
    force,
    label="Single Column",
    color="gray",
    linewidth=1.5,
    linestyle="--",
    zorder=2,
)
plt.plot(
    disp_3,
    force_3 + force_1[-1] + force_2[-1],
    linewidth=1.0,
    label="After Add",
    zorder=3,
)

plt.xlim(-0.4, 0.4)
plt.ylim(-500 * UNIT.kn, 500 * UNIT.kn)
plt.xlabel("Displacement (m)")
plt.ylabel("Force (kN)")
plt.legend(loc="lower right", bbox_to_anchor=(1.0, 0.0))
plt.grid(linestyle="--", linewidth=0.5, zorder=1)
plt.show()
Demaged Single Column Add Element Hysteretic Curve

Visualize model responses

fig = opsplt.plot_nodal_responses_animation(odb_tag=CASE_2, resp_type="disp", show_undeformed=True)
fig
# fig.show()
OPSTOOL™ ::  Loading responses data from G:\opstool\docs\.opstool.output\RespStepData-2.odb ...


Visualize final model (Case 2)

opsplt.set_plot_props(point_size=5, line_width=3)
fig = opsplt.plot_model(
    show_ele_numbering=True,
    show_local_axes=True,
)
fig
# fig.show()


Check zeroLength element

ODB_ele_Link = opst.post.get_element_responses(odb_tag=CASE_2, ele_type="Link", print_info=False)
print(f"ODB_ele_link.eleTags: {ODB_ele_Link.eleTags.values}")

# Check zeroLengthSection element
ODB_ele_sec = opst.post.get_element_responses(odb_tag=CASE_2, ele_type="FiberSection", print_info=False)
print(f"ODB_ele_sec.eleTags: {ODB_ele_sec.eleTags.values}")
ODB_ele_link.eleTags: [4 5]
ODB_ele_sec.eleTags: [1 2 3 4]
print(ODB_ele_sec.data_vars)
Data variables:
    areas     (eleTags, secPoints, fiberPoints) float64 53kB 4.339e-05 ... nan
    matTags   (eleTags, secPoints, fiberPoints) float64 53kB 10.0 10.0 ... nan
    secDefo   (time, eleTags, secPoints, DOFs) float32 269kB 0.0 -0.0 ... nan
    secForce  (time, eleTags, secPoints, DOFs) float32 269kB 0.0 0.0 ... nan nan
    Strains   (time, eleTags, secPoints, fiberPoints) float32 22MB 0.0 ... nan
    Stresses  (time, eleTags, secPoints, fiberPoints) float32 22MB 0.0 ... nan
    ys        (eleTags, secPoints, fiberPoints) float64 53kB 0.02253 ... nan
    zs        (eleTags, secPoints, fiberPoints) float64 53kB 0.002494 ... nan
print(ODB_ele_sec.data_vars["ys"])
<xarray.DataArray 'ys' (eleTags: 4, secPoints: 5, fiberPoints: 329)> Size: 53kB
array([[[ 0.02253355, -0.0359375 , -0.04575958, ...,         nan,
                 nan,         nan],
        [ 0.02253355, -0.0359375 , -0.04575958, ...,         nan,
                 nan,         nan],
        [ 0.02253355, -0.0359375 , -0.04575958, ...,         nan,
                 nan,         nan],
        [ 0.02253355, -0.0359375 , -0.04575958, ...,         nan,
                 nan,         nan],
        [ 0.02253355, -0.0359375 , -0.04575958, ...,         nan,
                 nan,         nan]],

       [[-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
...
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534],
        [-0.00894108, -0.00788283, -0.00798224, ...,  0.00123532,
          0.00951401,  0.00795534]],

       [[ 0.02253355, -0.0359375 , -0.04575958, ...,         nan,
                 nan,         nan],
        [        nan,         nan,         nan, ...,         nan,
                 nan,         nan],
        [        nan,         nan,         nan, ...,         nan,
                 nan,         nan],
        [        nan,         nan,         nan, ...,         nan,
                 nan,         nan],
        [        nan,         nan,         nan, ...,         nan,
                 nan,         nan]]], shape=(4, 5, 329))
Coordinates:
  * eleTags      (eleTags) int64 32B 1 2 3 4
  * secPoints    (secPoints) int64 40B 1 2 3 4 5
  * fiberPoints  (fiberPoints) int64 3kB 1 2 3 4 5 6 ... 324 325 326 327 328 329

Total running time of the script: (0 minutes 26.988 seconds)

Gallery generated by Sphinx-Gallery